# -*- coding: utf-8 -*-

# load in basic modules
import os
import string
import numpy as np
from math import pi
from psychopy import (data, visual, event)
from pylsl.pylsl import StreamInlet, resolve_byprop
from .utils import NeuroScanPort, _check_array_like
import threading
import time
from copy import copy,deepcopy

# prefunctions
def sinusoidal_sample(freqs, phases, srate, frames, stim_color):
    """Sinusoidal approximate sampling method.
    -author: Qiaoyi Wu
    -Created on: 2022-06-20 
    -update log:  
        2022-06-26 by Jianhang Wu 
        2022-08-10 by Wei Zhao
    Parameters
    ----------
        freqs: list of float,
            Frequencies of each stimulus.
        phases: list of float,
            Phases of each stimulus.
        srate: int or float,
            Refresh rate of screen.
        frames: int,
            Flashing frames.
        stim_color: list,
            Color of stimu.
    Returns:
    ----------
        color: ndarray,
            (n_frames, n_elements, 3)
    """

    time = np.linspace(0, (frames-1)/srate, frames)
    color = np.zeros((frames,len(freqs),3))
    for ne, (freq, phase) in enumerate(zip(freqs, phases)):
        sinw = np.sin(2*pi*freq*time + pi*phase) + 1
        color[:,ne,:] = np.vstack((sinw*stim_color[0], sinw*stim_color[1], sinw*stim_color[2])).T
        if stim_color==[-1,-1,-1]:
            pass
        else:
            if stim_color[0]==-1:
                color[:,ne,0] = -1
            if stim_color[1]==-1:
                color[:,ne,1] = -1
            if stim_color[2]==-1:
                color[:,ne,2] = -1
        
    return color

# create interface for VEP-BCI-Speller
class KeyboardInterface(object):
    """Create stimulus interface.
    -author: Qiaoyi Wu
    -Created on: 2022-06-20 
    -update log:  
        2022-06-26 by Jianhang Wu 
        2022-08-10 by Wei Zhao
    Parameters
    ----------
    win: 
        The window object.
    colorspace: str,
        The color space, default to rgb.
    allowGUI: bool
        Defaults to True, which allows frame-by-frame drawing and key-exit.
    """

    def __init__(self, win, colorSpace='rgb', allowGUI=True):
        self.win = win
        win.colorSpace = colorSpace
        win.allowGUI = allowGUI
        win_size = win.size
        self.win_size = np.array(win_size)   # e.g. [1920,1080]
    
    def config_pos(self, n_elements=40, rows=5, columns=8, stim_pos=None, stim_length=150, stim_width=150):
        """Config positions of stimuli.
        -update log:  
            2022-06-26 by Jianhang Wu 
        Parameters
        ----------
            n_elements: int,
                Number of stimuli.
            rows: int, optional,
                Rows of keyboard.
            columns: int, optional,
                Columns of keyboard.
            stim_pos: ndarray, optional,
                Extra position matrix.
            stim_length: int,
                Length of stimulus.
            stim_width: int,
                Width of stimulus.
        Raises
        ----------     
            Exception: Inconsistent numbers of stimuli and positions.
        """

        self.stim_length = stim_length
        self.stim_width = stim_width
        self.n_elements = n_elements
        # highly customizable position matrix
        if (stim_pos is not None) and (self.n_elements==stim_pos.shape[0]):
            # note that the origin point of the coordinate axis should be the center of your screen
            # (so the upper left corner is in Quadrant 2nd), and the larger the coordinate value,
            # the farther the actual position is from the center
            self.stim_pos = stim_pos
        # conventional design method
        elif (stim_pos is None) and (rows*columns>=self.n_elements):
            # according to the given rows of columns, coordinates will be automatically converted
            stim_pos = np.zeros((self.n_elements, 2))
            # divide the whole screen into rows*columns' blocks, and pick the center of each block
            first_pos = np.array([self.win_size[0]/columns, self.win_size[1]/rows]) / 2
            if (first_pos[0]<stim_length/2) or (first_pos[1]<stim_width/2):
                raise Exception('Too much blocks or too big the stimulus region!')
            for i in range(columns):
                for j in range(rows):
                    stim_pos[i*rows+j] = first_pos + [i,j]*first_pos*2
            # note that those coordinates are still not the real ones that need to be set on the screen
            stim_pos -= self.win_size/2  # from Quadrant 1st to 3rd
            stim_pos[:,1] *= -1  # invert the y-axis
            self.stim_pos = stim_pos
        else:
            raise Exception('Incorrect number of stimulus!')

        # check size of stimuli
        stim_sizes = np.zeros((self.n_elements, 2))
        stim_sizes[:] = np.array([stim_length, stim_width])
        self.stim_sizes = stim_sizes
        self.stim_width = stim_width
    
    def config_text(self, symbols=None, symbol_height=0, tex_color=[1,1,1]):
        """Config text stimuli.
        -update log:  
            2022-06-26 by Jianhang Wu 
        Parameters
        ----------
            symbols: list of str,
                Target characters.
            symbol_height: int,
                Height of target symbol.
            tex_color: list,
                Color of target symbol.
        Raises:
            Exception: Insufficient characters.
        """

        # check number of symbols
        if (symbols is not None) and (len(symbols)>=self.n_elements):
            self.symbols = symbols
        elif self.n_elements<=40:
            self.symbols = ''.join([string.ascii_uppercase, '1234567890+-*/'])
        else:
            raise Exception('Please input correct symbol list!')

        # add text targets onto interface
        if symbol_height == 0:
            symbol_height = self.stim_width/2
        self.text_stimuli = []
        for symbol, pos in zip(self.symbols, self.stim_pos):
            self.text_stimuli.append(visual.TextStim(win=self.win, text=symbol, font='Times New Roman', pos=pos,
                        color=tex_color, units='pix', height=symbol_height, bold=True, name=symbol))

    def config_response(self, symbol_text='Speller:  ',symbol_height=0, symbol_color=(1,1,1), bg_color=[-1, -1, -1]):
        """Config response stimuli.
        -update log:  
            2022-08-10 by Wei Zhao
        Parameters
        ----------
            symbol_text: list of str,
                Online response string.
            symbol_height: int,
                Height of response symbol.
            symbol_color: list,
                Color of response symbol.
            bg_color: list,
                Color of background symbol.
        Raises:
            Exception: Insufficient characters.
        """

        brige_length = self.win_size[0]/2+self.stim_pos[0][0]-self.stim_length/2
        brige_width = self.win_size[1]/2-self.stim_pos[0][1]-self.stim_width/2
       
        self.rect_response = visual.Rect(win=self.win, units="pix", width=self.win_size[0]-brige_length, height=brige_width*3/4,
                            pos=(0,self.win_size[1]/2-brige_width/2), fillColor=bg_color,
                            lineColor=[1, 1, 1])

        self.res_text_pos = (-self.win_size[0]/2+brige_length*3/2,self.win_size[1]/2-brige_width/2)
        self.reset_res_pos = (-self.win_size[0]/2+brige_length*3/2,self.win_size[1]/2-brige_width/2)
        self.reset_res_text = 'Speller:  '
        if symbol_height==0:
            self.symbol_height = brige_width/2
        self.symbol_text = symbol_text
        self.text_response = visual.TextStim(win=self.win, text=symbol_text, font='Times New Roman', 
                                        pos=self.res_text_pos, color=symbol_color, units='pix', 
                                        height=self.symbol_height, bold=True)
        
# config visual stimuli
class VisualStim(KeyboardInterface):
    """Create various visual stimuli.
    -author: Qiaoyi Wu
    -Created on: 2022-06-20 
    -update log:  
        2022-06-26 by Jianhang Wu 
    Parameters
    ----------
    win: 
        The window object.
    colorspace: str,
        The color space, default to rgb.
    allowGUI: bool
        Defaults to True, which allows frame-by-frame drawing and key-exit.
    """

    def __init__(self, win, colorSpace='rgb', allowGUI=True):
        super().__init__(win=win, colorSpace=colorSpace, allowGUI=allowGUI)
        self._exit = threading.Event()
        
    def config_index(self, index_height=0):
        """Config index stimuli: downward triangle (Unicode: \u2BC6)
        Parameters
        ----------
            index_height: int, optional,
                Defaults to 75 pixels.
        """

        # add index onto interface, with positions to be confirmed.
        if index_height == 0:
            index_height = copy(self.stim_width/3*2)
        self.index_stimuli = visual.TextStim(win=self.win, text='\u2BC6', font='Arial', color=[1.,1.,0.],
            colorSpace='rgb', units='pix', height=index_height, bold=True, autoLog=False)
 
# standard SSVEP paradigm
class SSVEP(VisualStim):
    """Create SSVEP stimuli.
    -author: Qiaoyi Wu
    -Created on: 2022-06-20 
    -update log:  
        2022-06-26 by Jianhang Wu 
        2022-08-10 by Wei Zhao
    Parameters
    ----------
    win: 
        The window object.
    colorspace: str,
        The color space, default to rgb.
    allowGUI: bool
        Defaults to True, which allows frame-by-frame drawing and key-exit.
    """
    
    def __init__(self, win, colorSpace='rgb', allowGUI=True):
        """Item class from VisualStim.

        Args:
            
        """
        super().__init__(win=win, colorSpace=colorSpace, allowGUI=allowGUI)

    def config_color(self, refresh_rate, stim_time, stim_color, stimtype='sinusoid', stim_opacities=1, **kwargs):
        """Config color of stimuli.
        Parameters
        ----------
            refresh_rate: int or float,
                Refresh rate of screen.
            stim_time: float,
                Time of each stimulus. 
            stim_frames: int,
                Flash frames of one trial.
            stim_colors: ndarray,
                (n_frames, n_elements, 3).
            stim_opacities: int or float,
                Opacities of each stimulus. 
            freqs: list of float,
                Frequencies of each stimulus.
            phases: list of float,
                Phases of each stimulus.
        Raises:
            Exception: Inconsistent frames and color matrices.
        """

        # initialize extra inputs   
        self.refresh_rate = refresh_rate
        self.stim_time = stim_time
        self.stim_color = stim_color
        self.stim_opacities = stim_opacities
        self.stim_frames = int(stim_time*self.refresh_rate)

        if refresh_rate==0:
            self.refresh_rate = np.floor(self.win.getActualFrameRate(nIdentical=20, nWarmUpFrames=20))
        
        self.stim_oris = np.zeros((self.n_elements,))       # orientation
        self.stim_sfs = np.zeros((self.n_elements,))        # spatial frequency
        self.stim_contrs = np.ones((self.n_elements,))      # contrast
        
        # check extra inputs
        if 'stim_oris' in kwargs.keys():
            self.stim_oris = kwargs['stim_oris']
        if 'stim_sfs' in kwargs.keys():
            self.stim_sfs = kwargs['stim_sfs']
        if 'stim_contrs' in kwargs.keys():
            self.stim_contrs = kwargs['stim_contrs']
        if 'freqs' in kwargs.keys():
            self.freqs = kwargs['freqs']
        if 'phases' in kwargs.keys():
            self.phases = kwargs['phases']
        
        # check consistency
        if (stimtype == 'sinusoid'):
            self.stim_colors = sinusoidal_sample(freqs=self.freqs, phases=self.phases,srate=self.refresh_rate,
                    frames=self.stim_frames,stim_color=stim_color)
            if (self.stim_colors[0].shape[0] != self.n_elements):
                 raise Exception('Please input correct num of stims!')   

        incorrect_frame = (self.stim_colors.shape[0]!=self.stim_frames)
        incorrect_number = (self.stim_colors.shape[1]!=self.n_elements)
        if incorrect_frame or incorrect_number:
            raise Exception('Incorrect color matrix or flash frames!')
        
        # add flashing targets onto interface
        self.flash_stimuli = []
        for sf in range(self.stim_frames):
            self.flash_stimuli.append(visual.ElementArrayStim(win=self.win, units='pix', nElements=self.n_elements,
                                                              sizes=self.stim_sizes, xys=self.stim_pos,
                                                              colors=self.stim_colors[sf,...], opacities=self.stim_opacities,
                                                              oris=self.stim_oris, sfs=self.stim_sfs, contrs=self.stim_contrs,
                                                              elementTex=np.ones((64,64)),elementMask=None, texRes=48))

# standard P300 paradigm
class P300(VisualStim):
    """Create P300 stimuli.
    -author: Shengfu Wen
    -Created on: 2022-07-04
    -update log: 
        2022-08-10 by Wei Zhao
    Parameters
    ----------
    win: 
        The window object.
    colorspace: str,
        The color space, default to rgb.
    allowGUI: bool
        Defaults to True, which allows frame-by-frame drawing and key-exit.
    """

    def __init__(self, win, colorSpace='rgb', allowGUI=True):
        super().__init__(win=win, colorSpace=colorSpace, allowGUI=allowGUI)

    def config_color(self, refresh_rate=0, stim_duration=0.5):
        """Config color of stimuli.
        Parameters
        ----------
            refresh_rate: int or float,
                Refresh rate of screen.
            symbol_height: float,
                Height of each stimulus. 
            stim_duration: float,
                The duration of one trial.
        """
        self.stim_duration=stim_duration
        self.refresh_rate = refresh_rate
        if refresh_rate==0:
            self.refresh_rate = np.floor(self.win.getActualFrameRate(nIdentical=20, nWarmUpFrames=20))
        

        # highlight one row/ column onto interface
        row_pos = np.unique(self.stim_pos[:, 0])
        col_pos = np.unique(self.stim_pos[:, 1])
        [row_num, col_num] = [len(col_pos), len(row_pos)]
        self.stim_frames = int((row_num + col_num) * stim_duration * refresh_rate)    # complete single trial

        row_order_index = list(range(0, row_num))
        np.random.shuffle(row_order_index)
        col_order_index = list(range(0, col_num))
        np.random.shuffle(col_order_index)                        
        l_row_order_index=[x+self.n_elements+1 for x in row_order_index] # reset event label
        l_col_order_index=[x+self.n_elements+row_num+1 for x in col_order_index]
        
        self.order_index=np.array(l_row_order_index+l_col_order_index)           # event label
        
        # Determine row and column char status
        stim_colors_row = np.zeros([(row_num * col_num), int(row_num * refresh_rate * stim_duration), 3])   
        stim_colors_col = np.zeros([(row_num * col_num), int(col_num * refresh_rate * stim_duration), 3])   # 

        tmp = 0
        for col_i in col_order_index:
            stim_colors_col[(col_i * row_num):((col_i+1) * row_num),
            int(tmp * refresh_rate * stim_duration):int((tmp + 1) * refresh_rate * stim_duration)] = [-1, -1, -1]
            tmp += 1

        tmp = 0
        for row_i in row_order_index:
            for col_i in range(col_num):
                stim_colors_row[(row_i + row_num * col_i),
                int(tmp * refresh_rate * stim_duration):int((tmp + 1) * refresh_rate * stim_duration)] = [-1, -1, -1]
            tmp += 1

        stim_colors = np.concatenate((stim_colors_row, stim_colors_col), axis=1)
        self.stim_colors = np.transpose(stim_colors, [1, 0, 2])

        # add flashing targets onto interface
        self.flash_stimuli = []
        for sf in range(self.stim_frames):
            self.flash_stimuli.append(visual.ElementArrayStim(win=self.win, units='pix', nElements=self.n_elements,
                                                             sizes=self.stim_sizes, xys=self.stim_pos, colors=self.stim_colors[sf,...],
                                                             elementTex=np.ones((64,64)), elementMask=None, texRes=48))

# standard MI paradigm
class MI(VisualStim):
    """Create MI stimuli.
    -author: Wei Zhao
    -Created on: 2022-06-30
    -update log: 
        2022-08-10 by Wei Zhao
    Parameters
    ----------
    win: 
        The window object.
    colorspace: str,
        The color space, default to rgb.
    allowGUI: bool
        Defaults to True, which allows frame-by-frame drawing and key-exit.
    """

    def __init__(self, win, colorSpace='rgb', allowGUI=True):
        super().__init__(win=win, colorSpace=colorSpace, allowGUI=allowGUI)

        self.tex_left = os.path.join(os.path.abspath(os.path.dirname(os.path.abspath(__file__))), 'textures\\left_hand.png')
        self.tex_right = os.path.join(os.path.abspath(os.path.dirname(os.path.abspath(__file__))), 'textures\\right_hand.png')
    
    def config_color(self, refresh_rate=60, text_pos=(0.0, 0.0), left_pos=[[-480, 0.0]], right_pos=[[480, 0.0]], tex_color=(1,-1,-1), 
                     normal_color=[[-0.8,-0.8, 0.8]], image_color=[[1,1,1]], symbol_height=100, n_Elements=1, stim_length=288, stim_width=162):
        """Config color of stimuli.
        Parameters
        ----------
            refresh_rate: int or float,
                Refresh rate of screen.
            text_pos: ndarray,
                Position of text. 
            left_pos: list,
                Position of left hand.
            right_pos: list,
                Position of right hand.
            text_color: list,
                Color of text.
            normal_color: list,
                Color of default stimulus
            image_color: list,
                Color of image or indicate stimulus.
            symbol_height: list,
                Height of text.
            n_Elements: list,
                Num of stimulus.
            stim_length: list,
                Length of stimulus.
            stim_width: list,
                Width of stimulus.
        """
        
        self.n_Elements = n_Elements
        self.stim_length = stim_length
        self.stim_width = stim_width
        self.left_pos = left_pos
        self.right_pos = right_pos
        self.refresh_rate = refresh_rate
        if refresh_rate==0:
            refresh_rate = np.floor(self.win.getActualFrameRate(nIdentical=20, nWarmUpFrames=20))
        
        if symbol_height == 0:
            symbol_height = int(self.win_size[1]/6)
        self.text_stimulus = visual.TextStim(self.win, text='start', font='Times New Roman', 
                pos=text_pos, color=tex_color, units='pix', height=symbol_height, bold=True)
        
        
        self.image_left_stimuli = visual.ElementArrayStim(self.win, units='pix', elementTex=self.tex_left, elementMask=None, texRes=2, nElements=n_Elements, 
                sizes=[[stim_length,stim_width]], xys=np.array(left_pos), oris=[0], colors=np.array(image_color), opacities=[1], contrs=[-1])
        self.image_right_stimuli = visual.ElementArrayStim(self.win, units='pix', elementTex=self.tex_right, elementMask=None, texRes=2, nElements=n_Elements, 
                sizes=[[stim_length,stim_width]], xys=np.array(right_pos), oris=[0], colors=np.array(image_color), opacities=[1], contrs=[-1])

        self.normal_left_stimuli = visual.ElementArrayStim(self.win, units='pix', elementTex=self.tex_left, elementMask=None, texRes=2, nElements=n_Elements, 
                sizes=[[stim_length,stim_width]], xys=np.array(left_pos), oris=[0], colors=np.array(normal_color), opacities=[1], contrs=[-1])
        self.normal_right_stimuli = visual.ElementArrayStim(self.win, units='pix', elementTex=self.tex_right, elementMask=None, texRes=2, nElements=n_Elements, 
                sizes=[[stim_length,stim_width]], xys=np.array(right_pos), oris=[0], colors=np.array(normal_color), opacities=[1], contrs=[-1])

    def config_response(self, response_color=[[-0.5,0.9,0.5]]):
        self.response_left_stimuli = visual.ElementArrayStim(self.win, units='pix', elementTex=self.tex_left, elementMask=None, texRes=2, nElements=self.n_Elements, 
                sizes=[[self.stim_length,self.stim_width]], xys=np.array(self.left_pos), oris=[0], colors=np.array(response_color), opacities=[1], contrs=[-1])
        self.response_right_stimuli = visual.ElementArrayStim(self.win, units='pix', elementTex=self.tex_right, elementMask=None, texRes=2, nElements=self.n_Elements, 
                sizes=[[self.stim_length,self.stim_width]], xys=np.array(self.right_pos), oris=[0], colors=np.array(response_color), opacities=[1], contrs=[-1])

# continuous experimen
class GetPlabel_MyTherad():
    """Start a thread that receives online results
    -author: Wei Zhao
    -Created on: 2022-07-30
    -update log: 
        2022-08-10 by Wei Zhao
    Parameters
    ----------
    inlet: 
        Stream data online.
    """

    def __init__(self, inlet):
        self.inlet = inlet
        self._exit = threading.Event()

    def feedbackThread(self):
        """Start the thread."""
        self._t_loop = threading.Thread(
            target=self._inner_loop, 
            name='get_predict_id_loop')
        self._t_loop.start()
    
    def _inner_loop(self):
        """The inner loop in the thread."""
        self._exit.clear()
        global online_text_pos,online_symbol_text
        online_text_pos = copy(self.res_text_pos)
        online_symbol_text = copy(self.symbol_text)
        while not self._exit.is_set():
            try:
                samples, _ = self.inlet.pull_sample()
                if samples:
                    predict_id = int(samples[0])-1            # online predict id
                    online_text_pos = (online_text_pos[0]+self.symbol_height/3,online_text_pos[1])
                    online_symbol_text = online_symbol_text + self.symbols[predict_id]  
            except:
                pass
    
    def stop_feedbackThread(self):
        """Stop the thread."""
        self._exit.set()
        self._t_loop.join() 
           
# basic experiment control
def paradigm(VSObject, win, bg_color, display_time=1., index_time=1., rest_time=0.5, response_time=2, image_time=2, 
             port_addr=9045, nrep=1, pdim='ssvep', lsl_source_id=None, online=None):
    """Passing outsied parameters to inner attributes.
    -author: Wei Zhao
    -Created on: 2022-07-30
    -update log: 
        2022-08-10 by Wei Zhao
        2022-08-03 by Shengfu Wen
    Parameters
    ----------
        bg_color: ndarray,
            Background color.
        display_time: float,
            Keyboard display time before 1st index.
        index_time: float,
            Indicator display time.
        rest_time: float, optional,
            Rest-state time.
        respond_time: float, optional,
            Feedback time during online experiment.
        image_time: float, optional,
            Image time.
        port_addr:
             Computer port.
        nrep: int, 
            Num of blocks.
        mi_flag: bool,
            Flag of MI paradigm.
        lsl_source_id: str,
            Source id.
        online: bool,
            Flag of online experiment.
    """
    
    if not _check_array_like(bg_color, 3):
        raise ValueError("bg_color should be 3 elements array-like object.")
    win.color = bg_color
    fps = VSObject.refresh_rate
    
    port = NeuroScanPort(port_addr, use_serial=False) if port_addr else None
    port_frame = int(0.05*fps)
    
    inlet = False 
    if online:    
        if pdim=='ssvep' or pdim=='p300' or pdim=='con-ssvep':
            VSObject.text_response.text = copy(VSObject.reset_res_text)
            VSObject.text_response.pos = copy(VSObject.reset_res_pos)
            VSObject.res_text_pos =copy(VSObject.reset_res_pos)
            VSObject.symbol_text = copy(VSObject.reset_res_text)  
            res_text_pos = VSObject.reset_res_pos
        if lsl_source_id:   
            inlet = True
            streams = resolve_byprop(    
                'source_id', lsl_source_id, timeout=5)  # Resolve all streams by source_id
            if not streams:
                return
            inlet = StreamInlet(streams[0])             # receive stream data
    
    if pdim=='ssvep':
        # config experiment settings
        conditions = [{'id': i} for i in range(VSObject.n_elements)]
        trials = data.TrialHandler(conditions, nrep, name='experiment', method='random')

        # start routine
        # episode 1: display speller interface
        iframe = 0
        while iframe < int(fps*display_time):
            if online:
                VSObject.rect_response.draw()
                VSObject.text_response.draw()
            for text_stimulus in VSObject.text_stimuli:
                text_stimulus.draw()
            iframe += 1
            win.flip()

        # episode 2: begin to flash
        if port:
            port.setData(0)
        for trial in trials: 
            # quit demo
            keys = event.getKeys(['q'])
            if 'q' in keys:
                break
            
            # initialise index position
            id = int(trial['id'])
            position = VSObject.stim_pos[id] + np.array([0, VSObject.stim_width/2])
            VSObject.index_stimuli.setPos(position)
    
            ## phase I: speller & index (eye shifting)
            iframe = 0
            while iframe < int(fps*index_time):
                if online:
                    VSObject.rect_response.draw()
                    VSObject.text_response.draw()
                for text_stimulus in VSObject.text_stimuli:
                    text_stimulus.draw()
                VSObject.index_stimuli.draw()
                iframe += 1
                win.flip()

            # phase II: rest state
            if rest_time != 0:
                iframe = 0
                while iframe < int(fps*rest_time):
                    if online:
                        VSObject.rect_response.draw()
                        VSObject.text_response.draw()
                    for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                    iframe += 1
                    win.flip()

            # phase III: target stimulating
            for sf in range(VSObject.stim_frames): 
                if sf==0 and port and online:
                    VSObject.win.callOnFlip(port.setData, 1)
                elif sf==0 and port:
                    VSObject.win.callOnFlip(port.setData, id+1)
                if sf==port_frame and port:
                    port.setData(0)
                VSObject.flash_stimuli[sf].draw()
                if online:
                    VSObject.rect_response.draw() 
                    VSObject.text_response.draw() 
                for text_stimulus in VSObject.text_stimuli:
                    text_stimulus.draw()
                win.flip()
            
            # phase IV: respond
            if inlet:
                VSObject.rect_response.draw()
                VSObject.text_response.draw()  
                for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                win.flip()
                samples, timestamp = inlet.pull_sample()
                predict_id = int(samples[0])-1            # online predict id
                VSObject.symbol_text = VSObject.symbol_text + VSObject.symbols[predict_id]
                res_text_pos = (res_text_pos[0]+VSObject.symbol_height/3,res_text_pos[1])
                iframe = 0
                while iframe < int(fps*response_time):
                    for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                    VSObject.rect_response.draw() 
                    VSObject.text_response.text = VSObject.symbol_text
                    VSObject.text_response.pos = res_text_pos
                    VSObject.text_response.draw()
                    iframe += 1
                    win.flip() 
    
    elif pdim=='p300':
        # config experiment settings
        conditions = [{'id': i} for i in range(VSObject.n_elements)]
        trials = data.TrialHandler(conditions, nrep, name='experiment', method='random')

        # start routine
        # episode 1: display speller interface
        iframe = 0
        while iframe < int(fps*display_time):
            if online:
                VSObject.rect_response.draw()
                VSObject.text_response.draw()
            for text_stimulus in VSObject.text_stimuli:
                text_stimulus.draw()
            iframe += 1
            win.flip()

        # episode 2: begin to flash
        if port:
            port.setData(0)
        for trial in trials: 
            # quit demo
            keys = event.getKeys(['q'])
            if 'q' in keys:
                break
            
            # initialise index position
            id = int(trial['id'])
            position = VSObject.stim_pos[id] + np.array([0, VSObject.stim_width/2])
            VSObject.index_stimuli.setPos(position)
    
            ## phase I: speller & index (eye shifting)
            iframe = 0
            while iframe < int(fps*index_time):
                if iframe==0 and port and online:
                    VSObject.win.callOnFlip(port.setData, 1)
                elif iframe==0 and port:
                    VSObject.win.callOnFlip(port.setData, id+1)
                if iframe==port_frame and port:
                    port.setData(0)                 
                if online:
                    VSObject.rect_response.draw()
                    VSObject.text_response.draw()
                for text_stimulus in VSObject.text_stimuli:
                    text_stimulus.draw()
                VSObject.index_stimuli.draw()
                iframe += 1
                win.flip()

            # phase II: rest state
            if rest_time != 0:
                iframe = 0
                while iframe < int(fps*rest_time):
                    if online:
                        VSObject.rect_response.draw()
                        VSObject.text_response.draw()
                    for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                    iframe += 1
                    win.flip()

            # phase III: target stimulating
            for sf in range(VSObject.stim_frames):
                if (sf%(VSObject.stim_duration*fps))==0 and port:
                    VSObject.win.callOnFlip(port.setData,
                        VSObject.order_index[int(sf/(VSObject.stim_duration*fps))])
                if (sf%(VSObject.stim_duration*fps))==port_frame and port:
                    port.setData(0)
                
                VSObject.flash_stimuli[sf].draw()
                if online:
                    VSObject.rect_response.draw() 
                    VSObject.text_response.draw() 
                for text_stimulus in VSObject.text_stimuli:
                    text_stimulus.draw()
                win.flip()

            # phase IV: respond
            if inlet:
                VSObject.rect_response.draw()
                VSObject.text_response.draw() 
                for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                win.flip()
                samples, timestamp = inlet.pull_sample()
                predict_id = int(samples[0])-1            # online predict id
                VSObject.symbol_text = VSObject.symbol_text + VSObject.symbols[predict_id]
                res_text_pos = (res_text_pos[0]+VSObject.symbol_height/3,res_text_pos[1])
                iframe = 0
                while iframe < int(fps*response_time):
                    for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                    VSObject.rect_response.draw() 
                    VSObject.text_response.text = VSObject.symbol_text
                    VSObject.text_response.pos = res_text_pos
                    VSObject.text_response.draw()
                    iframe += 1
                    win.flip() 
                    
    elif pdim=='mi':
        # config experiment settings
        conditions = [
            {'id': 0, 'name': 'left_hand'},
            {'id': 1, 'name': 'right_hand'},
            {'id': 2, 'name': 'both_hands'}
        ]
        trials = data.TrialHandler(conditions, nrep, name='experiment', method='random')
        
        # start routine
        # episode 1: display speller interface
        iframe = 0
        while iframe < int(fps*display_time):
            VSObject.normal_left_stimuli.draw()
            VSObject.normal_right_stimuli.draw()
            iframe += 1
            win.flip()

        # episode 2: begin to flash
        if port:
            port.setData(0)
        for trial in trials: 
            # quit demo
            keys = event.getKeys(['q'])
            if 'q' in keys:
                break
            
            # initialise index position
            id = int(trial['id'])
            if id == 0:
                image_stimuli = [VSObject.image_left_stimuli]
                normal_stimuli = [VSObject.normal_right_stimuli]
            elif id == 1:
                image_stimuli = [VSObject.image_right_stimuli]
                normal_stimuli = [VSObject.normal_left_stimuli]
            else:
                image_stimuli = [VSObject.image_left_stimuli,VSObject.image_right_stimuli]
                normal_stimuli = []
            
            # phase I: rest state
            if rest_time != 0:
                iframe = 0
                while iframe < int(fps*rest_time):
                    VSObject.normal_left_stimuli.draw()
                    VSObject.normal_right_stimuli.draw()
                    iframe += 1
                    win.flip()

            ## phase II: speller & index (eye shifting)
            iframe = 0
            while iframe < int(fps*index_time):
                for _image_stimuli in image_stimuli:
                    _image_stimuli.draw()
                if normal_stimuli:
                    for _normal_stimuli in normal_stimuli:
                        _normal_stimuli.draw()
                iframe += 1
                win.flip()

            # phase III: target stimulating
            iframe = 0
            while iframe < int(fps*image_time): 
                if iframe==0 and port and online:
                    VSObject.win.callOnFlip(port.setData, 1)
                elif iframe==0 and port:
                    VSObject.win.callOnFlip(port.setData, id+1)
                if iframe==port_frame and port:
                    port.setData(0)
                VSObject.text_stimulus.draw()
                for _image_stimuli in image_stimuli:
                    _image_stimuli.draw()
                if normal_stimuli:
                    for _normal_stimuli in normal_stimuli:
                        _normal_stimuli.draw()
                iframe += 1
                win.flip()

            # phase IV: respond
            if inlet:
                VSObject.normal_left_stimuli.draw()
                VSObject.normal_right_stimuli.draw()
                win.flip()
                
                samples, timestamp = inlet.pull_sample()
                predict_id = int(samples[0])-1            # online predict id
                
                if predict_id==0:
                    response_stimuli = [VSObject.response_left_stimuli]
                    normal_stimuli = [VSObject.normal_right_stimuli]
                elif predict_id==1:
                    response_stimuli = [VSObject.response_right_stimuli]
                    normal_stimuli = [VSObject.normal_left_stimuli]
                else:
                    response_stimuli = [VSObject.response_left_stimuli,VSObject.response_right_stimuli]
                    normal_stimuli = []

                iframe = 0
                while iframe < int(fps*response_time):
                    for _response_stimuli in response_stimuli:
                        _response_stimuli.draw()
                    if normal_stimuli:
                        for _normal_stimuli in normal_stimuli:
                            _normal_stimuli.draw()
                    iframe += 1
                    win.flip() 

    elif pdim=='con-ssvep':
        global online_text_pos,online_symbol_text
        
        if inlet:
            MyTherad = GetPlabel_MyTherad(inlet)
            MyTherad.feedbackThread()
            
        # config experiment settings
        conditions = [{'id': i} for i in range(VSObject.n_elements)]
        trials = data.TrialHandler(conditions, nrep, name='experiment', method='random')

        # start routine
        # episode 1: display speller interface
        iframe = 0
        while iframe < int(fps*display_time):
            if online:
                VSObject.rect_response.draw()
                VSObject.text_response.draw()
            for text_stimulus in VSObject.text_stimuli:
                text_stimulus.draw()
            iframe += 1
            win.flip()

        # episode 2: begin to flash
        if port:
            port.setData(0)
        for trial in trials: 
            # quit demo
            keys = event.getKeys(['q'])
            if 'q' in keys:
                MyTherad.stop_feedbackThread()
                break  
            
            # initialise index position
            id = int(trial['id'])
            position = VSObject.stim_pos[id] + np.array([0, VSObject.stim_width/2])
            VSObject.index_stimuli.setPos(position)
            
            ## phase I: speller & index (eye shifting)
            iframe = 0
            while iframe < int(fps*index_time):
                if online:
                    VSObject.rect_response.draw()
                    VSObject.text_response.text = online_symbol_text
                    VSObject.text_response.pos = online_text_pos
                    VSObject.text_response.draw()
                for text_stimulus in VSObject.text_stimuli:
                    text_stimulus.draw()
                VSObject.index_stimuli.draw()
                iframe += 1
                win.flip()

            # phase II: rest state
            if rest_time != 0:
                iframe = 0
                while iframe < int(fps*rest_time):
                    if online:
                        VSObject.rect_response.draw()
                        VSObject.text_response.text = online_symbol_text
                        VSObject.text_response.pos = online_text_pos
                        VSObject.text_response.draw()
                    for text_stimulus in VSObject.text_stimuli:
                        text_stimulus.draw()
                    iframe += 1
                    win.flip()

            # phase III: target stimulating
            for sf in range(VSObject.stim_frames): 
                if sf==0 and port and online:
                    VSObject.win.callOnFlip(port.setData, 1)
                elif sf==0 and port:
                    VSObject.win.callOnFlip(port.setData, id+1)
                if sf==port_frame and port:
                    port.setData(0)
                VSObject.flash_stimuli[sf].draw()
                if online:
                    VSObject.rect_response.draw()
                    VSObject.text_response.text = online_symbol_text
                    VSObject.text_response.pos = online_text_pos
                    VSObject.text_response.draw()
                for text_stimulus in VSObject.text_stimuli:
                    text_stimulus.draw()
                win.flip()
            
        if inlet:
            MyTherad.stop_feedbackThread()


